package com.yzi.core.encode
{
	import com.yzi.core.thread.IRunnable;
	import com.yzi.core.thread.Thread;
	
	import flash.display.BitmapData;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.utils.ByteArray;
	
	public class PNGEncoder extends Thread implements IEventDispatcher
	{
		private var m_bitmapData:BitmapData;
		private var m_dispatcher:EventDispatcher;
		
		private var m_crcTable:Array;
		private var m_crcTableComputed:Boolean;
		
		private var m_byteArray:ByteArray;
		
		public function PNGEncoder()
		{
			init();
		}
		
		private function init(): void
		{
			m_byteArray = new ByteArray();
			m_crcTable = new Array();
			m_crcTableComputed = false;
		}
		
		private function writeChunk($png:ByteArray, $type:uint, $data:ByteArray): void
		{
			if (!m_crcTableComputed) {
				m_crcTableComputed = true;
				m_crcTable = [];
				var c:uint;
				for (var n:uint = 0; n < 256; ++n)
				{
					c = n;
					for (var k:uint = 0; k < 8; ++k)
					{					
						if (c & 1) {
							c = uint(uint(0xedb88320) ^ uint(c >>> 1));
						} else {
							c = uint(c >>> 1);
						}
					}
					m_crcTable[n] = c;
				}
			}
			var len:uint = 0;
			if ($data != null) {
				len = $data.length;
			}
			$png.writeUnsignedInt(len);
			
			var p:uint = $png.position;
			$png.writeUnsignedInt($type);
			if ( $data != null ) {
				$png.writeBytes($data);
			}
			var e:uint = $png.position;
			
			$png.position = p;
			c = 0xffffffff;
			
			for (var i:int = 0; i < (e - p); ++i)
			{
				c = uint(m_crcTable[(c ^ $png.readUnsignedByte()) & uint(0xff)] ^ uint(c >>> 8));
			}
			c = uint(c ^ uint(0xffffffff));
			$png.position = e;
			$png.writeUnsignedInt(c);
		}
		
		public function Encode($image:BitmapData): void
		{
			m_bitmapData = $image;
			
			// Write PNG signature
			m_byteArray.writeUnsignedInt(0x89504e47);
			m_byteArray.writeUnsignedInt(0x0D0A1A0A);
			
			// Build IHDR chunk
			var IHDR:ByteArray = new ByteArray();
			IHDR.writeInt($image.width);
			IHDR.writeInt($image.height);
			IHDR.writeUnsignedInt(0x08060000); // 32bit RGBA
			IHDR.writeByte(0);
			writeChunk(m_byteArray, 0x49484452, IHDR);
			
			// Build IDAT chunk
			var IDAT:ByteArray= new ByteArray();
			for(var i:int = 0; i < $image.height; ++i)
			{
				// no filter
				IDAT.writeByte(0);
				var p:uint;
				var j:int;
				if (!$image.transparent) {
					for(j = 0; j < $image.width; ++j)
					{
						p = $image.getPixel(j, i);
						IDAT.writeUnsignedInt(uint(((p & 0xFFFFFF) << 8) | 0xFF));
					}
				} else {
					for(j = 0; j < $image.width; ++j)
					{
						p = $image.getPixel32(j, i);
						IDAT.writeUnsignedInt(uint(((p & 0xFFFFFF) << 8) | (p >>> 24)));
					}
				}
			}
			IDAT.compress();
			writeChunk(m_byteArray, 0x49444154, IDAT);
			// Build IEND chunk
			writeChunk(m_byteArray, 0x49454E44, null);
			// return m_byteArray
		}
		
		public function GetByteArray(): ByteArray { return m_byteArray; }
		
		public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void
		{
			m_dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
		}
		
		public function removeEventListener(type:String, listener:Function, useCapture:Boolean=false):void
		{
			m_dispatcher.removeEventListener(type, listener, useCapture);
		}
		
		public function dispatchEvent(event:Event):Boolean
		{
			return m_dispatcher.dispatchEvent(event);
		}
		
		public function hasEventListener(type:String):Boolean
		{
			return m_dispatcher.hasEventListener(type);
		}
		
		public function willTrigger(type:String):Boolean
		{
			return m_dispatcher.willTrigger(type);
		}
	}
}